"""
增强版Pydantic模型定义，完全符合user_ck_schema.csv规范
"""
from abc import ABC, abstractmethod
from collections.abc import Generator
from pandas.core.groupby.ops import DataSplitter
from pydantic import BaseModel, Field, field_validator, model_validator
from typing import Dict, List, Optional, Tuple, Any
from datetime import datetime, timedelta
from enum import Enum
from pathlib import Path
import random
import json
import numpy as np

class BaseGenerator(ABC):
    '''数据生成器抽象类'''
    def __init__(self):
        pass

    @abstractmethod
    def generator(self) -> Generator[Any, Any, Any]:
        '''生成器基类必须有generator()方法'''
        pass

    def _sample_from_distribution(self, distribution: Dict[Any, float]) -> any:
        """从分布中采样"""
        items = list(distribution.keys())
       
        probabilities = list(distribution.values())
        selected = random.choices(items, weights=probabilities, k=1)[0]
        return selected

class PackageInfo(BaseModel):
    """套餐信息模型"""
    name: str = Field(..., description="套餐名称")
    data: int = Field(..., description="套餐流量(GB)", ge=0)
    call: int = Field(..., description="套餐通话时长(分钟)", ge=0)
    fee: float = Field(..., description="套餐费用(元)", ge=0)
    operator: str = Field(..., description="运营商")
    
    def __str__(self):
        return f"{self.operator}-{self.name}({self.data}GB/{self.call}分钟/{self.fee}元)"


class PackageInfoGenerator:
    """套餐信息生成器"""
    
    def __init__(self, nums: int):
        self.nums = nums
        # 套餐信息 (按运营商分类)
        self.package_info = {
            '移动': [
                {'name': '移动大王卡', 'data': 30, 'call': 100, 'fee': 29},
                {'name': '全球通尊享套餐', 'data': 50, 'call': 500, 'fee': 128},
                {'name': '神州行畅聊卡', 'data': 5, 'call': 300, 'fee': 19},
                {'name': '5G智享套餐', 'data': 100, 'call': 1000, 'fee': 199},
                {'name': '和飞享套餐', 'data': 20, 'call': 200, 'fee': 39}
            ],
            '电信': [
                {'name': '电信无忧卡', 'data': 10, 'call': 50, 'fee': 19},
                {'name': '十全十美套餐', 'data': 40, 'call': 400, 'fee': 99},
                {'name': '天翼畅享套餐', 'data': 60, 'call': 800, 'fee': 129},
                {'name': '5G融合套餐', 'data': 120, 'call': 1500, 'fee': 199},
                {'name': '青年一派卡', 'data': 30, 'call': 200, 'fee': 29}
            ],
            '联通': [
                {'name': '联通大王卡', 'data': 40, 'call': 0, 'fee': 19},
                {'name': '冰淇淋套餐', 'data': 100, 'call': 1000, 'fee': 129},
                {'name': '沃派套餐', 'data': 20, 'call': 300, 'fee': 39},
                {'name': '5G畅爽套餐', 'data': 80, 'call': 500, 'fee': 99},
                {'name': '校园套餐', 'data': 30, 'call': 200, 'fee': 29}
            ]
        }
        
        # 运营商权重分布
        self.operator_weights = {
            '移动': 0.6,  # 60%的用户选择移动
            '电信': 0.15, # 25%的用户选择电信
            '联通': 0.25  # 15%的用户选择联通
        }
    
    def _select_operator(self) -> str:
        """根据权重选择运营商"""
        operators = list(self.operator_weights.keys())
        weights = list(self.operator_weights.values())
        return random.choices(operators, weights=weights, k=1)[0]
    
    def _select_package(self, operator: str) -> PackageInfo:
        """为指定运营商选择套餐"""
        packages = self.package_info[operator]
        selected_package = random.choice(packages)
        return PackageInfo(
            name=selected_package['name'],
            data=selected_package['data'],
            call=selected_package['call'],
            fee=selected_package['fee'],
            operator=operator
        )
    
    def generator(self):
        """生成套餐信息"""
        for i in range(self.nums):
            # 选择运营商
            operator = self._select_operator()
            # 选择套餐
            package = self._select_package(operator)
            yield package
    
    def get_package_by_operator(self, operator: str) -> PackageInfo:
        """根据运营商获取套餐"""
        return self._select_package(operator)
    
    def get_all_packages(self) -> List[PackageInfo]:
        """获取所有套餐信息"""
        all_packages = []
        for operator, packages in self.package_info.items():
            for package in packages:
                all_packages.append(PackageInfo(
                    name=package['name'],
                    data=package['data'],
                    call=package['call'],
                    fee=package['fee'],
                    operator=operator
                ))
        return all_packages

class GenderType(str, Enum):
    """
    性别枚举类型

    用于表示用户的性别属性，常用于数据建模、数据生成、数据校验等场景。
    该枚举类有两个成员：
        - MALE: "男"
        - FEMALE: "女"

    用法示例:
        # 1. 作为Pydantic模型字段类型
        class User(BaseModel):
            gender: GenderType

        # 2. 赋值与判断
        user_gender = GenderType.MALE
        if user_gender == GenderType.FEMALE:
            print("女性用户")

        # 3. 从字符串转换
        gender = GenderType("男")  # 得到GenderType.MALE

    注意事项:
        - 该枚举值与user_ck_schema.csv中的性别字段保持一致。
        - 推荐在所有涉及性别的字段中统一使用本枚举，避免硬编码字符串。
    """
    MALE = "男"
    FEMALE = "女"


class GenderGenerator(BaseGenerator):
    def __init__(self, nums: int):
        self.nums = nums
        self.gender_probabilities = {
            GenderType.MALE: 0.51,
            GenderType.FEMALE: 0.49
        }
    
    def generator(self):
        for i in range(self.nums):
            selected = self._sample_from_distribution(self.gender_probabilities)
            yield GenderType(selected)


class AgeGroup(str, Enum):
    """年龄段枚举"""
    UNDER_18 = "18岁以下"
    AGE_18_25 = "18-25岁"
    AGE_26_30 = "26-30岁"
    AGE_31_35 = "31-35岁"
    AGE_35_45 = "35-45岁"
    AGE_46_60 = "46-60岁"
    OVER_60 = "60岁以上"

class AgeGroupGenerator(BaseGenerator):
    def __init__(self, nums: int):
        self.nums = nums
        self.age_group_probabilities = {
            AgeGroup.UNDER_18: 0.1,
            AgeGroup.AGE_18_25: 0.15,
            AgeGroup.AGE_26_30: 0.2,
            AgeGroup.AGE_31_35: 0.15,
            AgeGroup.AGE_35_45: 0.2,
            AgeGroup.AGE_46_60: 0.15,
            AgeGroup.OVER_60: 0.05
        }
    
    def generator(self):
        for i in range(self.nums):
            selected = self._sample_from_distribution(self.age_group_probabilities)
            yield AgeGroup(selected)

class TrendType(str, Enum):
    """趋势类型枚举"""
    STABLE = "稳定型"
    GROWTH = "增长型"
    DECLINE = "下降型"
    FLUCTUATION = "波动型"


class FluctuationLabel(str, Enum):
    """资费波动标签枚举"""
    STABLE = "稳定型"
    GROWTH = "增长型"
    DECLINE = "下降型"
    FLUCTUATION = "波动型"

class FluctuationLabelGenerator(BaseGenerator):
    def __init__(self, nums: int):
        self.nums = nums
        self.fluctuation_label_probabilities = {
            FluctuationLabel.STABLE: 0.2,
            FluctuationLabel.GROWTH: 0.2,
            FluctuationLabel.DECLINE: 0.4,
            FluctuationLabel.FLUCTUATION: 0.2
        }
    
    def generator(self):
        for i in range(self.nums):
            selected = self._sample_from_distribution(self.fluctuation_label_probabilities)
            yield FluctuationLabel(selected)


class ProvinceType(str, Enum):
    """省份枚举"""
    BEIJING = "北京"
    SHANDONG = "山东"
    YUNNAN = "云南"

class ResidenceInfo(BaseModel):
    """居住地数据信息模型"""
    PROVINCE: ProvinceType = Field(..., description="省份(直辖市)")
    CITY: str = Field(..., description="市(区)")
    COUNTY: str = Field(..., description="区县街道")
    BLOCK: Optional[str] = Field(None, description="小区名")

    def __str__(self):
        return f"{self.PROVINCE.value}{self.CITY}{self.COUNTY}{self.BLOCK if self.BLOCK else ''}"

class ResidenceInfoGenerator(BaseGenerator):
    def __init__(self, nums: int):
        self.nums = nums
    
    def generator(self):
        beijing_districts = self._load_beijing_districts()
        district_probabilities = self._create_district_probabilities(beijing_districts)
        districts = list(district_probabilities.keys())
        probs = list(district_probabilities.values())
        
        for i in range(self.nums):
            #selected_district = random.choices(districts, weights=probs, k=1)[0]
            selected_district = self._sample_from_distribution(district_probabilities)
            selected_block = random.choices(beijing_districts[selected_district], k=1)[0]
            
            yield ResidenceInfo(
                PROVINCE=ProvinceType.BEIJING,
                CITY=selected_district,
                COUNTY=selected_block,
                BLOCK=None
            )
        
    
    def _load_beijing_districts(self) -> Dict[str, List[str]]:
        """从JSON文件加载北京市各区县街道数据"""
        json_path = Path(__file__).parent.parent / 'data' / 'beijing_blocks.json'
        try:
            with open(str(json_path), 'r', encoding='utf-8') as f:
                data = json.load(f)
            districts_data = {k: v for k, v in data.items() if k != 'metadata'}
            return districts_data
        except FileNotFoundError:
            print(f"警告: 未找到文件 {json_path}")
            print("使用默认的beijing_districts数据")
            return {
                "东城区": ["东华门街道", "景山街道", "交道口街道", "安定门街道"],
                "西城区": ["西长安街街道", "新街口街道", "月坛街道", "展览路街道"],
                "朝阳区": ["建外街道", "朝外街道", "呼家楼街道", "三里屯街道"]
            }

    def _create_district_probabilities(self, beijing_districts: Dict[str, List[str]]) -> Dict[str, float]:
        """创建区县分布概率"""
        districts = list(beijing_districts.keys())
        num_districts = len(districts)
    
        if num_districts == 0:
            return {}
    
        # 创建基础概率数组
        if num_districts >= 16:
            probs = np.array([0.08, 0.08, 0.15, 0.1, 0.05, 0.15, 0.05, 0.05, 0.08, 0.05, 0.03, 0.08, 0.03, 0.02, 0.02, 0.02])
            if num_districts > 16:
                extra_prob = 0.01
                probs = np.concatenate([probs, [extra_prob] * (num_districts - 16)])
                print(probs)
        else:
            probs = np.ones(num_districts) / num_districts
    
        # 归一化
        probs = probs / probs.sum()
    
        return dict(zip(districts, probs))

class GridInfo(BaseModel):
    """网格信息模型"""
    GRID_ID: str = Field(..., description="网格ID")
    GRID_LAT: float = Field(..., description="网格经度")
    GRID_LONG: float = Field(..., description="网格纬度")

class GridInfoGenerator(BaseGenerator):
    def __init__(self, prefix: str, grid_num: int=100):
        self.prefix = prefix
        self.grid_num = grid_num
        self.base_mapping = {
            f"{prefix}{str(i+1).zfill(3)}": {
                "LAT": round(random.uniform(39.43, 41.05), 2),
                "LONG": round(random.uniform(115.25, 117.30), 2)
            } for i in range(grid_num)
        }
    
    def generator(self):
        for i in range(self.grid_num):
            grid_id = f"{self.prefix}{str(i+1).zfill(3)}"
            yield GridInfo(
                GRID_ID=grid_id,
                GRID_LAT=self.base_mapping.get(grid_id, {}).get("LAT"),
                GRID_LONG=self.base_mapping.get(grid_id, {}).get("LONG")
            )




class UserRecord(BaseModel):
    """完整用户记录模型，符合user_ck_schema.csv规范"""
    
    # 基础标识字段
    STATIS_TIME: str = Field(..., description="统计时间")
    PROV: ProvinceType = Field(..., description="省份名称")
    PERSON_ID: str = Field(..., description="自然人编码")
    PHONE_NUM: Optional[str] = Field(None, description="手机号码")
    USER_ID: Optional[str] = Field(None, description="用户编码")
    CUST_ID: Optional[str] = Field(None, description="客户编码")
    
    # 用户属性字段
    ATTR_AGE: Optional[int] = Field(None, description="属性-年龄", ge=0, le=120)
    ATTR_GENDER: Optional[GenderType] = Field(None, description="属性-性别")
    ATTR_PROVINCE: str = Field(default="北京市", description="属性-省")
    ATTR_CITY: str = Field(..., description="属性-市")
    ATTR_COUNTY: str = Field(..., description="属性-区县街道")
    
    # 网格和基站信息
    ATTR_GRID_ID: Optional[str] = Field(None, description="属性-所属网格编码")
    ATTR_GRID_LAT: Optional[float] = Field(None, description="属性-所属网格经度", ge=39.43, le=41.05)
    ATTR_GRID_LONG: Optional[float] = Field(None, description="属性-所属网格纬度", ge=115.25, le=117.30)
    ATTR_DAY_BASE_ID: Optional[str] = Field(None, description="属性-日间常驻基站编码")
    ATTR_DAY_BASE_LAT: Optional[float] = Field(None, description="属性-日间常驻基站经度", ge=39.43, le=41.05)
    ATTR_DAY_BASE_LONG: Optional[float] = Field(None, description="属性-日间常驻基站纬度", ge=115.25, le=117.30)
    ATTR_NIGHT_BASE_ID: Optional[str] = Field(None, description="属性-夜间常驻基站编码")
    ATTR_NIGHT_BASE_LAT: Optional[float] = Field(None, description="属性-夜间常驻基站经度", ge=39.43, le=41.05)
    ATTR_NIGHT_BASE_LONG: Optional[float] = Field(None, description="属性-夜间常驻基站纬度", ge=115.25, le=117.30)

    # 业务属性
    ATTR_FEE_FLUCTUATION: Optional[FluctuationLabel] = Field(None, description="属性-资费波动标签")

    # 标签字段
    LABEL_IS_GSM: Optional[int] = Field(None, description="标签-是否全球通用户", ge=0, le=1)
    LABEL_IS_MID: Optional[int] = Field(None, description="标签-是否腰部用户", ge=0, le=1)
    LABEL_IS_PREM: Optional[int] = Field(None, description="标签-是否中高端用户", ge=0, le=1)
    LABEL_IS_DIFF: int = Field(..., description="标签-是否异网用户", ge=0, le=1)
    LABEL_IS_ELDER: Optional[int] = Field(None, description="标签-是否银发用户", ge=0, le=1)
    LABEL_IS_INT: Optional[int] = Field(None, description="标签-是否国际用户", ge=0, le=1)
    

    # 社交圈信息
    ATTR_FAMILY_CIRCLE_ID: Optional[str] = Field(None, description="属性-家庭圈ID")
    ATTR_SOCIAL_CIRCLE_ID: Optional[str] = Field(None, description="属性-交往圈ID")
    ATTR_COMMUNITY_CIRCLE_ID: Optional[str] = Field(None, description="属性-社区圈ID")
    ATTR_CAMPUS_CIRCLE_ID: Optional[str] = Field(None, description="属性-校园圈ID")
    ATTR_COMMUTE_CIRCLE_ID: Optional[str] = Field(None, description="属性-通勤圈ID")
    ATTR_BUSINESS_CIRCLE_ID: Optional[str] = Field(None, description="属性-商业圈ID")
    ATTR_ONLINE_CIRCLE_ID: Optional[str] = Field(None, description="属性-网络圈ID")
    
    # 指标字段
    METRIC_ARPU: Optional[float] = Field(None, description="指标-ARPU（元）", ge=0)
    METRIC_FLUX_USED: Optional[float] = Field(None, description="指标-使用流量（MB）", ge=0)

    ## 家宽指标
    METRIC_BROADBAND_COUNT: int = Field(default=0, description="属性_家宽条数", ge=0)
    METRIC_BROADBAND_TRAFFIC: float = Field(default=0.0, description="属性_家宽平均流量(KB)", ge=0)
    METRIC_BROADBAND_ONILNETIME: float = Field(default=0.0, description="属性_家宽在线时长(秒)", ge=0)

    METRIC_MONTHLY_CONSUMPTION: Optional[float] = Field(None, description="指标-月消费金额(元)", ge=0)
    METRIC_BASIC_PACKAGE_FEE: Optional[float] = Field(None, description="指标-基本套餐费(元)", ge=0)
    METRIC_LOCAL_CALL_FEE: Optional[float] = Field(None, description="指标-本地通话费（元）", ge=0)
    METRIC_LONG_DISTANCE_CALL_FEE: Optional[float] = Field(None, description="指标-异地通话费（元）", ge=0)
    METRIC_DOMESTIC_DATA_FEE: Optional[float] = Field(None, description="指标-国内流量包业务费（元）", ge=0)
    METRIC_INTERNATIONAL_DATA_FEE: Optional[float] = Field(None, description="指标-国际流量包业务费（元）", ge=0)
    METRIC_ADDITIONAL_SERVICE_FEE: Optional[float] = Field(None, description="指标-附加业务费（元）", ge=0)
    METRIC_VALUE_ADDED_SERVICE_FEE: Optional[float] = Field(None, description="指标-增值业务费（元）", ge=0)
    METRIC_DATA_USAGE_FEE: Optional[float] = Field(None, description="指标-流量使用费（元）", ge=0)

    METRIC_CALL_DURATION_MINUTES: Optional[int] = Field(None, description="指标-通话时长（分钟）", ge=0)


    @field_validator('ATTR_AGE')
    @classmethod
    def validate_age(cls, v):
        if v is not None and (v < 0 or v > 120):
            raise ValueError('年龄必须在0-120之间')
        return v

    @field_validator('STATIS_TIME')
    @classmethod
    def validate_statis_time(cls, v):
        if len(v) != 8 or not v.isdigit():
            raise ValueError('统计时间格式必须为YYYYMMDD')
        return v

class UserRecordV2(UserRecord):
    """
    用户记录V2版本，扩展了更多业务字段
    
    在UserRecord基础上增加了套餐信息、基站使用统计、家宽信息、
    产品使用情况、集团信息等更详细的业务字段。
    """
    
    # 套餐相关属性
    ATTR_NUMBER_ACTIVATION_DATE: Optional[str] = Field(None, description="属性-激活日期")
    ATTR_NUMBER_PLAN_NAME: Optional[str] = Field(None, description="属性-套餐名称")
    ATTR_NUMBER_TERMINATION_DATE: Optional[str] = Field(None, description="属性-离网日期")
    ATTR_NUMBER_REGISTERED_LOCATION: Optional[str] = Field(None, description="属性-入网地址")
    
    # 套餐指标
    METRIC_NUMBER_DATA_ALLOWANCE_MB: Optional[int] = Field(None, description="指标-套餐总流量（MB）", ge=0)
    METRIC_NUMBER_FREE_CALL_MINUTES: Optional[int] = Field(None, description="指标-套餐总通话时长（分钟）", ge=0)
    
    # 携号转网标签
    LABEL_NUMBER_PORTABILITY_STATUS: Optional[str] = Field(None, description="标签-是否携号转网")
    
    # 基站使用统计
    METRIC_LAC_DAY_TOTAL_TIME_MINUTES: Optional[int] = Field(None, description="指标-白天常驻基站时间（分钟）", ge=0)
    METRIC_LAC_NIGHT_TOTAL_TIME_MINUTES: Optional[int] = Field(None, description="指标-夜间常驻基站时间（分钟）", ge=0)
    METRIC_LAC_DAY_LAC_COUNT: Optional[int] = Field(None, description="指标-白天常驻基站连接次数", ge=0)
    METRIC_LAC_NIGHT_LAC_COUNT: Optional[int] = Field(None, description="指标-夜间常驻基站连接次数", ge=0)
    
    # 家宽信息
    ATTR_BRD_ADDR: Optional[str] = Field(None, description="属性-家宽地址")
    
    # 产品使用信息
    ATTR_PRODUCT_NAME: Optional[str] = Field(None, description="属性-使用产品名称")
    
    # 集团信息
    ATTR_GROUP_NAME: Optional[str] = Field(None, description="属性-所属集团名称")
    
    # 信用分
    ATTR_CREDIT_SCORE: Optional[int] = Field(None, description="属性-信用分总得分", ge=420, le=696)
    
    
    @field_validator('LABEL_NUMBER_PORTABILITY_STATUS')
    @classmethod
    def validate_portability_status(cls, v):
        """验证携号转网状态"""
        if v is not None and v not in ['是', '否']:
            raise ValueError('携号转网状态必须为"是"或"否"')
        return v


class AgeBaseParams(BaseModel):
    """
    年龄段基础参数配置类
    
    这个类定义了不同年龄段用户的基础消费行为参数，是移动资费数据模拟的核心配置。
    每个年龄段都有其独特的消费特征，包括流量使用、通话时长、消费趋势等。
    
    用途：
    - 为不同年龄段的用户提供基础消费参数
    - 支持个性化数据生成，模拟真实用户的消费行为差异
    - 作为数据生成算法的输入参数，影响最终生成的资费数据
    
    依赖关系：
    - 被依赖：UserGenerationParams, DistributionConfig, EnhancedUser, EnhancedMobileDataSimulator
    - 依赖：pydantic.BaseModel, pydantic.Field, pydantic.field_validator
    
    使用场景：
    1. 在 UserGenerationParams 中为单个用户指定年龄段参数
    2. 在 DistributionConfig 中为所有年龄段定义默认参数
    3. 在 EnhancedUser 中用于计算用户的消费数据
    4. 在 EnhancedMobileDataSimulator 中作为默认配置的一部分
    
    示例用法：
    ```python
    # 创建26-30岁年龄段的基础参数
    age_params = AgeBaseParams(
        traffic=50,      # 基础流量50GB
        call=300,        # 基础通话300分钟
        growth=0.05,     # 5%的增长率
        decline=0.01,    # 1%的下降率
        package=158      # 基础套餐费158元
    )
    
    # 在用户生成参数中使用
    user_params = UserGenerationParams(
        basic_info=basic_info,
        age_base_params=age_params,  # 使用年龄段参数
        num_months=6
    )
    ```
    
    字段说明：
    - traffic: 基础流量使用量，影响用户的数据消费行为
    - call: 基础通话时长，影响用户的语音消费行为  
    - growth: 增长率，影响用户消费的增长趋势
    - decline: 下降率，影响用户消费的下降趋势
    - package: 基础套餐费，影响用户的基础资费水平
    """
    traffic: float = Field(..., description="基础流量使用量(GB)")
    call: float = Field(..., description="基础通话时长(分钟)")
    growth: float = Field(..., description="增长率")
    decline: float = Field(..., description="下降率")
    package: float = Field(..., description="基础套餐费(元)")
    
    @field_validator('traffic', 'call', 'package')
    @classmethod
    def must_be_positive(cls, v):
        """验证流量、通话时长和套餐费必须为正数"""
        if v <= 0:
            raise ValueError('必须为正数')
        return v
    
    @field_validator('growth', 'decline')
    @classmethod
    def must_be_valid_rate(cls, v):
        """验证增长率和下降率必须在0-1之间"""
        if not 0 <= v <= 1:
            raise ValueError('增长率和下降率必须在0-1之间')
        return v


class UserBasicInfo(BaseModel):
    """
    用户基本信息类
    
    这个类定义了用户的基本属性信息，包括用户标识、个人特征、地理位置和消费趋势等。
    它是用户数据生成的基础，为后续的数据模拟提供用户的基本特征参数。
    
    用途：
    - 定义用户的唯一标识信息
    - 描述用户的基本属性特征
    - 指定用户的地理位置信息
    - 预设用户的消费趋势类型
    
    依赖关系：
    - 被依赖：UserGenerationParams, EnhancedUser
    - 依赖：pydantic.BaseModel, pydantic.Field, GenderType, AgeGroup, TrendType
    
    使用场景：
    1. 在 UserGenerationParams 中作为用户的基本信息输入
    2. 在 EnhancedUser 中用于生成用户的详细记录
    3. 在数据生成过程中作为用户特征的基础数据
    
    示例用法：
    ```python
    # 创建用户基本信息
    basic_info = UserBasicInfo(
        USER_ID="U100001",
        PERSON_ID="P100001", 
        ATTR_GENDER=GenderType.MALE,
        age_group=AgeGroup.AGE_26_30,
        ATTR_CITY="北京市",
        district="朝阳区",
        ATTR_COUNTY="建外街道",
        trend_type=TrendType.GROWTH
    )
    
    # 在用户生成参数中使用
    user_params = UserGenerationParams(
        basic_info=basic_info,
        age_base_params=age_params,
        num_months=6
    )
    ```
    
    字段说明：
    - USER_ID: 用户唯一标识符，用于区分不同用户
    - PERSON_ID: 自然人编码，对应真实用户的身份标识
    - ATTR_GENDER: 用户性别，影响消费行为模式
    - age_group: 用户年龄段，决定基础消费参数
    - ATTR_CITY: 用户所在城市，影响地理位置相关数据
    - district: 用户所在区县，用于区域分布统计
    - ATTR_COUNTY: 用户所在街道，提供更精确的地理位置
    - trend_type: 预设趋势类型，影响消费数据的变化模式
    """
    USER_ID: str = Field(..., description="用户编码")
    PERSON_ID: str = Field(..., description="自然人编码")
    ATTR_GENDER: GenderType = Field(..., description="属性-性别")
    age_group: AgeGroup = Field(..., description="用户年龄段")
    ATTR_CITY: str = Field(..., description="属性-市")  # 不设默认值，由具体区县填入
    district: str = Field(..., description="用户所在区")
    ATTR_COUNTY: str = Field(..., description="属性-区县街道")
    trend_type: FluctuationLabel = Field(..., description="预设趋势类型")

class UserInfo(BaseModel):
    """
    用户信息类
    
    这个类整合了用户的基本信息和生成参数，是用户数据生成的核心配置类。
    它将用户的基本信息、年龄段参数、生成时间范围等参数组合在一起，
    为单个用户的数据生成提供完整的配置信息。
    """
    user_id : str = Field(..., description="用户编码")
    person_id: str = Field(..., description="自然人编码")
    cust_id: str = Field(..., description="客户编码")
    is_diff: int = Field(..., description="是否异网用户")
    phone_num: str = Field(..., description="手机号")
    gender: GenderType = Field(..., description="属性-性别")
    age_group: AgeGroup = Field(..., description="用户年龄段")
    residence_info: ResidenceInfo = Field(..., description="居住地信息")
    trend_type: FluctuationLabel = Field(..., description="预设趋势类型")

    grid_info: GridInfo = Field(..., description="网格信息")
    day_base_info: GridInfo = Field(..., description="基础信息")
    night_base_info: GridInfo = Field(..., description="夜间基础信息")

class UserInfoWithCalulate(UserInfo):
    """
    用户信息类，包含计算后的字段
    """
    age: int = Field(..., description="用户年龄")
    age_params: AgeBaseParams = Field(..., description="用户年龄段参数")
    package_info: PackageInfo = Field(..., description="用户套餐信息")

class UserInfoGenerator(BaseGenerator):
    def __init__(self, nums: int):
        self.nums = nums

    def _generate_random_string(self, prefix: str, length: int, start: int = 1) -> str:
        """生成随机字符串"""
        return f"{prefix}{str(start + random.randint(0, int('9' * length))).zfill(length)}"
    
    def _generate_phone_number(self) -> str:
        # """生成手机号码"""
        # prefixes = ["139", "138", "137", "136", "135", "134", "159", "158", "157", "150"]
        # prefix = random.choice(prefixes)
        # suffix = str(random.randint(00000000, 99999999))  # 直接生成8位随机数
        # return prefix + suffix
        """生成手机号码"""
        prefixes = ["139", "138", "137", "136", "135", "134", "147", "153", "154", "155", "156", "159", "158", "157",
                    "150", "151", "152", "182", "183", "184", "185", "186", "187", "188", "198"]
        return random.choice(prefixes) + ''.join([str(random.randint(0, 9)) for _ in range(8)])
    # 生成基本信息
        user_id = f"U{100000 + user_index}"
        person_id = f"P{100000 + user_index}"

    def generator(self):
        gender_generator = GenderGenerator(nums=self.nums).generator()
        age_group_generator = AgeGroupGenerator(nums=self.nums).generator()
        residence_info_generator = ResidenceInfoGenerator(nums=self.nums).generator()
        trend_type_generator = FluctuationLabelGenerator(nums=self.nums).generator()
        grid_info_generator = GridInfoGenerator("G", grid_num=self.nums).generator()
        base_info_generator = GridInfoGenerator("B", grid_num=self.nums).generator()
        is_diff_generator = random.choices([0, 1], weights=[80, 20], k=self.nums)
        
        
        for i, gender, age_group, residence_info, trend_type, grid_info, base_info, is_diff in zip(
            range(self.nums),
            gender_generator,
            age_group_generator,
            residence_info_generator,
            trend_type_generator,
            grid_info_generator,
            base_info_generator,
            is_diff_generator,
        ):
            
            user_id = f"U{10000 + i}"
            person_id = f"P{10000 + i}"
            cust_id = f"C{10000 + i}"
            phone = self._generate_phone_number()
            yield UserInfo(
                user_id=user_id,
                person_id=person_id, 
                cust_id=cust_id, 
                is_diff=is_diff,
                phone_num=phone, 
                gender=gender, 
                age_group=age_group, 
                residence_info=residence_info, 
                trend_type=trend_type, 
                grid_info=grid_info, 
                day_base_info=base_info, 
                night_base_info=base_info
            )

class UserInfoWithCalulateGenerator(UserInfoGenerator):

    def _cal_age(self, age_group: AgeGroup) -> int:
        """将年龄段转换为具体年龄"""
        age_ranges = {
            AgeGroup.UNDER_18: (16, 17),
            AgeGroup.AGE_18_25: (18, 25),
            AgeGroup.AGE_26_30: (26, 30),
            AgeGroup.AGE_31_35: (31, 35),
            AgeGroup.AGE_35_45: (35, 45),
            AgeGroup.AGE_46_60: (46, 60),
            AgeGroup.OVER_60: (60, 80)
        }
        min_age, max_age = age_ranges[age_group]
        return random.randint(min_age, max_age)
    
    def _get_age_params(self, age_group: AgeGroup) -> AgeBaseParams:
        """根据年龄段生成对应的基础参数"""
        age_params_map = {
            AgeGroup.UNDER_18: AgeBaseParams(
                traffic=20,      # 18岁以下流量使用较少
                call=150,        # 通话时长适中
                growth=0.08,     # 较高增长率
                decline=0.02,    # 较低下降率
                package=29       # 基础套餐费较低
            ),
            AgeGroup.AGE_18_25: AgeBaseParams(
                traffic=50,      # 年轻人流量使用较多
                call=300,        # 通话时长较多
                growth=0.12,     # 高增长率
                decline=0.01,    # 低下降率
                package=59       # 中等套餐费
            ),
            AgeGroup.AGE_26_30: AgeBaseParams(
                traffic=60,      # 流量使用较多
                call=400,        # 通话时长较多
                growth=0.10,     # 较高增长率
                decline=0.015,   # 较低下降率
                package=89       # 中高端套餐费
            ),
            AgeGroup.AGE_31_35: AgeBaseParams(
                traffic=55,      # 流量使用适中
                call=350,        # 通话时长适中
                growth=0.08,     # 中等增长率
                decline=0.02,    # 中等下降率
                package=79       # 中端套餐费
            ),
            AgeGroup.AGE_35_45: AgeBaseParams(
                traffic=45,      # 流量使用适中
                call=250,        # 通话时长适中
                growth=0.05,     # 较低增长率
                decline=0.025,   # 较高下降率
                package=69       # 中端套餐费
            ),
            AgeGroup.AGE_46_60: AgeBaseParams(
                traffic=30,      # 流量使用较少
                call=200,        # 通话时长较少
                growth=0.03,     # 低增长率
                decline=0.03,    # 高下降率
                package=49       # 中低端套餐费
            ),
            AgeGroup.OVER_60: AgeBaseParams(
                traffic=15,      # 老年人流量使用很少
                call=100,        # 通话时长很少
                growth=0.02,     # 很低增长率
                decline=0.04,    # 很高下降率
                package=29       # 低端套餐费
            )
        }
        return age_params_map[age_group]
    
    def generator(self):
        package_generator = PackageInfoGenerator(nums=self.nums).generator()
        for user_info in super().generator():
            age = self._cal_age(user_info.age_group)
            age_params = self._get_age_params(user_info.age_group)
            package_info = next(package_generator)
            yield UserInfoWithCalulate(
                **user_info.model_dump(),
                age=age,
                age_params=age_params,
                package_info=package_info
            )

class DayUserGenerator(BaseGenerator):
    def __init__(self, days: List[datetime], depend_params: UserInfoWithCalulate):
        self.days = days
        self.params = depend_params

    def _calculate_metrics(self, day_idx: int, age_params, package_info) -> Dict:
        """计算各项指标"""
        # 获取趋势参数
        trend_params = self._get_trend_params(self.params.trend_type, age_params)
        
        # 计算基础指标
        package_fee = self._calculate_package_fee(day_idx, package_info.fee, trend_params)
        traffic_usage_gb = self._calculate_traffic_usage(day_idx, age_params.traffic, trend_params, self.params.trend_type)
        call_duration_minutes = self._calculate_call_duration(day_idx, age_params.call, trend_params)
        
        # 计算各项费用
        local_call_fee, long_distance_call_fee = self._calculate_call_fees(call_duration_minutes)
        domestic_data_fee, international_data_fee = self._calculate_data_fees(traffic_usage_gb, self.params.age_group)
        additional_service_fee = self._calculate_additional_service_fee(trend_params)
        value_added_service_fee = self._calculate_value_added_service_fee(trend_params)
        
        # 总费用
        monthly_consumption = (
            package_fee + local_call_fee + long_distance_call_fee + 
            domestic_data_fee + international_data_fee + 
            additional_service_fee + value_added_service_fee
        )
        
        # ARPU和流量（KB）
        arpu = monthly_consumption
        flux_used = traffic_usage_gb * 1024  # 转换为MB
        
        # 家宽指标（基于用户特征）
        broadband_count = self._calculate_broadband_count(self.params.age, self.params.gender)
        broadband_traffic = self._calculate_broadband_traffic(broadband_count, self.params.age)
        broadband_online_time = self._calculate_broadband_online_time(broadband_count, self.params.age)
        
        return {
            'package_fee': package_fee,
            'traffic_usage_gb': traffic_usage_gb,
            'call_duration_minutes': call_duration_minutes,
            'local_call_fee': local_call_fee,
            'long_distance_call_fee': long_distance_call_fee,
            'domestic_data_fee': domestic_data_fee,
            'international_data_fee': international_data_fee,
            'additional_service_fee': additional_service_fee,
            'value_added_service_fee': value_added_service_fee,
            'monthly_consumption': monthly_consumption,
            'arpu': arpu,
            'flux_used': flux_used,
            'broadband_count': broadband_count,
            'broadband_traffic': broadband_traffic,
            'broadband_online_time': broadband_online_time
        }
    
    def _get_trend_params(self, trend_type: TrendType, age_params) -> Dict:
        """根据趋势类型获取参数调整"""
        if age_params is None:
            random_factor = np.random.uniform(0.8, 1.5)
            age_params = type('MockAgeParams', (), {
                'growth': random_factor,
                'decline': random_factor * 0.1
            })()
        
        if trend_type == TrendType.GROWTH:
            return {
                'traffic_growth_rate': age_params.growth * 1.5,
                'call_decline_rate': age_params.decline * 0.5,
                'fluctuation_level': 0.04,
                'package_adjust_rate': 0.12,
                'package_adjust_period': 2,
                'additional_service_range': (10, 15),
                'value_added_prob': 0.5
            }
        elif trend_type == TrendType.STABLE:
            return {
                'traffic_growth_rate': age_params.growth * 0.2,
                'call_decline_rate': age_params.decline * 0.2,
                'fluctuation_level': 0.02,
                'package_adjust_rate': 0.05,
                'package_adjust_period': 12,
                'additional_service_range': (8, 12),
                'value_added_prob': 0.1
            }
        elif trend_type == TrendType.DECLINE:
            return {
                'traffic_growth_rate': -age_params.growth * 1.5,
                'call_decline_rate': age_params.decline * 3,
                'fluctuation_level': 0.05,
                'package_adjust_rate': -0.08,
                'package_adjust_period': 3,
                'additional_service_range': (3, 8),
                'value_added_prob': 0.02
            }
        else:  # FLUCTUATION
            return {
                'traffic_growth_rate': age_params.growth * np.random.choice([0.5, 1.5]),
                'call_decline_rate': age_params.decline * np.random.choice([0.5, 1.5]),
                'fluctuation_level': 0.06,
                'package_adjust_rate': 0.1,
                'package_adjust_period': 3,
                'additional_service_range': (5, 15),
                'value_added_prob': 0.3
            }
    
    def _calculate_package_fee(self, day_idx: int, base_package: float, trend_params: Dict) -> float:
        """计算套餐费用"""
        package_adjust = 1 + trend_params['package_adjust_rate'] * (day_idx // trend_params['package_adjust_period'])
        package_fee = base_package * package_adjust
        return max(package_fee, base_package * 0.3)
    
    def _calculate_traffic_usage(self, day_idx: int, base_traffic: float, trend_params: Dict, trend_type: TrendType) -> float:
        """计算流量使用量"""
        traffic_growth = (1 + trend_params['traffic_growth_rate']) ** day_idx
        traffic_reduction = 1.0
        if trend_type == TrendType.DECLINE and np.random.random() < 0.3:
            traffic_reduction = np.random.uniform(0.7, 0.8)
        
        traffic = (base_traffic/27) * traffic_growth * traffic_reduction
        traffic *= (1 + np.random.uniform(-trend_params['fluctuation_level'], trend_params['fluctuation_level']))
        return max(traffic, 0)
    
    def _calculate_call_duration(self, day_idx: int, base_call: float, trend_params: Dict) -> int:
        """计算通话时长（分钟）"""
        call_decline = (1 - trend_params['call_decline_rate']) ** day_idx
        call = base_call * call_decline
        call *= (1 + np.random.uniform(-trend_params['fluctuation_level'], trend_params['fluctuation_level']))
        return max(int(call), 0)
    
    def _calculate_call_fees(self, call_duration: int) -> tuple:
        """计算通话费用"""
        local_call_ratio = np.random.uniform(0.6, 0.8)
        local_call_fee = call_duration * local_call_ratio * 0.1
        long_distance_call_fee = call_duration * (1 - local_call_ratio) * 0.3
        return local_call_fee, long_distance_call_fee
    
    def _calculate_data_fees(self, traffic_usage: float, age_group: AgeGroup) -> tuple:
        """计算流量费用"""
        domestic_data = traffic_usage * np.random.uniform(0.95, 1.0)
        
        if age_group not in [AgeGroup.UNDER_18, AgeGroup.OVER_60]:
            domestic_data_fee = max(0, (domestic_data - 100) * 1)
        else:
            domestic_data_fee = 0
        
        international_data_fee = 0
        if (np.random.random() < 0.05 and age_group not in [AgeGroup.UNDER_18, AgeGroup.OVER_60]):
            international_data_fee = (traffic_usage - domestic_data) * 10
        
        return domestic_data_fee, international_data_fee
    
    def _calculate_additional_service_fee(self, trend_params: Dict) -> float:
        """计算附加业务费"""
        min_fee, max_fee = trend_params['additional_service_range']
        return np.random.uniform(min_fee, max_fee)
    
    def _calculate_value_added_service_fee(self, trend_params: Dict) -> float:
        """计算增值业务费"""
        if np.random.random() < trend_params['value_added_prob']:
            return np.random.uniform(10, 50)
        return 0
    
    def _calculate_broadband_count(self, age: int, gender: GenderType) -> int:
        """计算家宽条数"""
        # 年轻人更可能有家宽
        if age < 30:
            return np.random.choice([0, 1, 2], p=[0.2, 0.6, 0.2])
        elif age < 50:
            return np.random.choice([0, 1, 2], p=[0.3, 0.5, 0.2])
        else:
            return np.random.choice([0, 1], p=[0.4, 0.6])
    
    def _calculate_broadband_traffic(self, broadband_count: int, age: int) -> float:
        """计算家宽流量"""
        if broadband_count == 0:
            return 0.0
        
        # 年轻人使用流量更多
        base_traffic = 200000 if age < 30 else 150000 if age < 50 else 100000
        return base_traffic * broadband_count * np.random.uniform(0.8, 1.2)
    
    def _calculate_broadband_online_time(self, broadband_count: int, age: int) -> float:
        """计算家宽在线时长"""
        if broadband_count == 0:
            return 0.0
        
        # 年轻人在线时长更长
        base_time = 20000 if age < 30 else 15000 if age < 50 else 10000
        return base_time * broadband_count * np.random.uniform(0.8, 1.2)

    def generator(self):
        for i in range(len(self.days)):
            # 计算指标（只有非异网用户才计算）
            if self.params.is_diff == 0:
                metrics = self._calculate_metrics(i, self.params.age_params, self.params.package_info)
            else:
                # 异网用户所有指标置空
                metrics = {
                    'package_fee': None, 'traffic_usage_gb': None, 'call_duration_minutes': None,
                    'local_call_fee': None, 'long_distance_call_fee': None,
                    'domestic_data_fee': None, 'international_data_fee': None,
                    'additional_service_fee': None, 'value_added_service_fee': None,
                    'monthly_consumption': None, 'arpu': None, 'flux_used': None,
                    'broadband_count': 0, 'broadband_traffic': 0.0, 'broadband_online_time': 0.0
                }
            
            # 生成V2版本的扩展字段
            activation_date = (self.days[i] - timedelta(days=random.randint(365, 1825))).strftime("%Y%m%d") if self.params.is_diff == 0 else None
            termination_date = None
            if self.params.is_diff == 0 and random.random() < 0.05:  # 5%的用户离网
                termination_date = (self.days[i] + timedelta(days=random.randint(1, 30))).strftime("%Y%m%d")
            
            # 基站使用统计
            day_lac_time = random.randint(480, 720) if self.params.is_diff == 0 else None  # 8-12小时
            night_lac_time = random.randint(360, 480) if self.params.is_diff == 0 else None  # 6-8小时
            day_lac_count = random.randint(5, 15) if self.params.is_diff == 0 else None
            night_lac_count = random.randint(3, 8) if self.params.is_diff == 0 else None
            
            # 信用分（420-696）
            credit_score = random.randint(420, 696) if self.params.is_diff == 0 else None
            
            yield UserRecordV2(
                # 基础标识字段
                STATIS_TIME=self.days[i].strftime("%Y%m%d"),
                PROV=self.params.residence_info.PROVINCE.value,
                PERSON_ID=self.params.person_id,
                PHONE_NUM=self.params.phone_num,
                USER_ID=self.params.user_id if self.params.is_diff == 0 else None,
                CUST_ID=self.params.cust_id,
                
                # 用户属性字段
                ATTR_AGE=self.params.age if self.params.is_diff == 0 else None,
                ATTR_GENDER=self.params.gender if self.params.is_diff == 0 else None,
                ATTR_PROVINCE=self.params.residence_info.PROVINCE.value,
                ATTR_CITY=self.params.residence_info.CITY,
                ATTR_COUNTY=self.params.residence_info.COUNTY,
                
                # 网格和基站信息
                ATTR_GRID_ID=self.params.grid_info.GRID_ID,
                ATTR_GRID_LAT=self.params.grid_info.GRID_LAT,
                ATTR_GRID_LONG=self.params.grid_info.GRID_LONG,
                ATTR_DAY_BASE_ID=self.params.day_base_info.GRID_ID,
                ATTR_DAY_BASE_LAT=self.params.day_base_info.GRID_LAT,
                ATTR_DAY_BASE_LONG=self.params.day_base_info.GRID_LONG,
                ATTR_NIGHT_BASE_ID=self.params.night_base_info.GRID_ID,
                ATTR_NIGHT_BASE_LAT=self.params.night_base_info.GRID_LAT,
                ATTR_NIGHT_BASE_LONG=self.params.night_base_info.GRID_LONG,
                
                # 业务属性
                ATTR_FEE_FLUCTUATION=self.params.trend_type,
                
                # 标签字段
                LABEL_IS_GSM=np.random.randint(0, 2) if self.params.is_diff == 0 else None,
                LABEL_IS_MID=np.random.randint(0, 2) if self.params.is_diff == 0 else None,
                LABEL_IS_PREM=np.random.randint(0, 2) if self.params.is_diff == 0 else None,
                LABEL_IS_DIFF=self.params.is_diff,
                LABEL_IS_ELDER=np.random.randint(0, 2) if self.params.is_diff == 0 else None,
                LABEL_IS_INT=np.random.randint(0, 2) if self.params.is_diff == 0 else None,
                
                # 社交圈信息
                ATTR_FAMILY_CIRCLE_ID=f"FC{i:03d}",
                ATTR_SOCIAL_CIRCLE_ID=f"SC{i:03d}",
                ATTR_COMMUNITY_CIRCLE_ID=f"CC{i:03d}",
                ATTR_CAMPUS_CIRCLE_ID=f"CP{i:03d}",
                ATTR_COMMUTE_CIRCLE_ID=f"CM{i:03d}",
                ATTR_BUSINESS_CIRCLE_ID=f"BC{i:03d}",
                ATTR_ONLINE_CIRCLE_ID=f"OC{i:03d}",
                
                # 指标字段
                METRIC_ARPU=metrics['arpu'],
                METRIC_FLUX_USED=metrics['flux_used'],
                
                # 家宽指标
                METRIC_BROADBAND_COUNT=metrics['broadband_count'],
                METRIC_BROADBAND_TRAFFIC=metrics['broadband_traffic'],
                METRIC_BROADBAND_ONILNETIME=metrics['broadband_online_time'],
                
                # 消费指标
                METRIC_MONTHLY_CONSUMPTION=metrics['monthly_consumption'],
                METRIC_BASIC_PACKAGE_FEE=metrics['package_fee'],
                METRIC_LOCAL_CALL_FEE=metrics['local_call_fee'],
                METRIC_LONG_DISTANCE_CALL_FEE=metrics['long_distance_call_fee'],
                METRIC_DOMESTIC_DATA_FEE=metrics['domestic_data_fee'],
                METRIC_INTERNATIONAL_DATA_FEE=metrics['international_data_fee'],
                METRIC_ADDITIONAL_SERVICE_FEE=metrics['additional_service_fee'],
                METRIC_VALUE_ADDED_SERVICE_FEE=metrics['value_added_service_fee'],
                METRIC_DATA_USAGE_FEE=metrics['traffic_usage_gb'],
                METRIC_CALL_DURATION_MINUTES=metrics['call_duration_minutes'],
                
                # V2版本扩展字段
                # 套餐相关属性
                ATTR_NUMBER_ACTIVATION_DATE=activation_date,
                ATTR_NUMBER_PLAN_NAME=self.params.package_info.name if self.params.is_diff == 0 else None,
                ATTR_NUMBER_TERMINATION_DATE=termination_date,
                ATTR_NUMBER_REGISTERED_LOCATION=f"{self.params.residence_info.PROVINCE.value}{self.params.residence_info.CITY}{self.params.residence_info.COUNTY}" if self.params.is_diff == 0 else None,
                
                # 套餐指标
                METRIC_NUMBER_DATA_ALLOWANCE_MB=self.params.package_info.data * 1024 if self.params.is_diff == 0 else None,  # 转换为MB
                METRIC_NUMBER_FREE_CALL_MINUTES=self.params.package_info.call if self.params.is_diff == 0 else None,
                
                # 携号转网标签
                LABEL_NUMBER_PORTABILITY_STATUS="否" if self.params.is_diff == 0 else None,  # 默认非携号转网
                
                # 基站使用统计
                METRIC_LAC_DAY_TOTAL_TIME_MINUTES=day_lac_time,
                METRIC_LAC_NIGHT_TOTAL_TIME_MINUTES=night_lac_time,
                METRIC_LAC_DAY_LAC_COUNT=day_lac_count,
                METRIC_LAC_NIGHT_LAC_COUNT=night_lac_count,
                
                # 家宽信息
                ATTR_BRD_ADDR=f"{self.params.residence_info.PROVINCE.value}{self.params.residence_info.CITY}{self.params.residence_info.COUNTY}小区{random.randint(1, 100)}号楼{random.randint(1, 20)}层{random.randint(1, 4)}室" if metrics['broadband_count'] > 0 else None,
                
                # 产品使用信息
                ATTR_PRODUCT_NAME="移动5G套餐" if self.params.is_diff == 0 else None,
                
                # 集团信息
                ATTR_GROUP_NAME=None,  # 大部分用户不属于集团
                
                # 信用分
                ATTR_CREDIT_SCORE=credit_score
            )

class UserGenerationParams(BaseModel):
    """
    用户数据生成参数类
    
    这个类整合了用户的基本信息和生成参数，是用户数据生成的核心配置类。
    它将用户的基本信息、年龄段参数、生成时间范围等参数组合在一起，
    为单个用户的数据生成提供完整的配置信息。
    
    用途：
    - 整合用户的基本信息和生成参数
    - 控制数据生成的时间范围和数量
    - 提供个性化的数据生成配置
    - 支持单个用户的定制化数据生成
    
    依赖关系：
    - 被依赖：EnhancedUser, EnhancedMobileDataSimulator
    - 依赖：UserBasicInfo, AgeBaseParams, pydantic.BaseModel, pydantic.Field
    
    使用场景：
    1. 在 EnhancedUser 中作为数据生成的主要输入参数
    2. 在 EnhancedMobileDataSimulator 中用于批量用户生成
    3. 在测试中用于创建特定的用户数据生成配置
    
    示例用法：
    ```python
    # 创建用户生成参数
    user_params = UserGenerationParams(
        basic_info=basic_info,           # 用户基本信息
        age_base_params=age_params,      # 年龄段基础参数
        num_months=6,                    # 生成6个月数据
        start_date=datetime(2024, 1, 1), # 开始日期
        individual_factor=1.2            # 个人差异系数
    )
    
    # 在增强用户类中使用
    user = EnhancedUser(user_params)
    records = user.generate_user_records()
    ```
    
    字段说明：
    - basic_info: 用户基本信息，包含用户标识、属性、地理位置等
    - age_base_params: 年龄段基础参数，定义消费行为的基础参数
    - num_months: 生成月数，控制生成数据的时间跨度
    - start_date: 开始日期，指定数据生成的时间起点
    - individual_factor: 个人差异系数，用于个性化调整消费行为
    """
    basic_info: UserBasicInfo
    age_base_params: AgeBaseParams
    num_months: int = Field(default=24, description="生成月数")
    start_date: datetime = Field(default_factory=lambda: datetime(2023, 7, 1), description="开始日期")
    individual_factor: float = Field(default=1.0, description="个人差异系数", ge=0.5, le=2.0)
    grid_mapping: GridInfo = Field(..., description="网格映射")
    base_mapping: GridInfo = Field(..., description="基础映射")
    
    @field_validator('num_months')
    @classmethod
    def validate_months(cls, v):
        """验证生成月数必须在合理范围内"""
        if v <= 0 or v > 120:
            raise ValueError('月数必须在1-120之间')
        return v


class DistributionConfig(BaseModel):
    """
    分布配置类
    
    这个类定义了用户群体的分布配置，包括年龄、性别、趋势类型、地理位置等维度的概率分布。
    它是批量用户生成的核心配置，确保生成的用户群体符合预期的分布特征。
    
    用途：
    - 定义用户群体的分布特征
    - 控制批量用户生成的统计特性
    - 确保生成数据的真实性和合理性
    - 支持不同场景下的用户群体模拟
    
    依赖关系：
    - 被依赖：SimulatorConfig, EnhancedMobileDataSimulator
    - 依赖：AgeGroup, GenderType, TrendType, AgeBaseParams, pydantic.BaseModel, pydantic.Field
    
    使用场景：
    1. 在 SimulatorConfig 中作为模拟器的分布配置
    2. 在 EnhancedMobileDataSimulator 中用于批量用户生成
    3. 在数据生成过程中确保用户群体的统计特征
    
    示例用法：
    ```python
    # 创建分布配置
    distribution_config = DistributionConfig(
        age_probabilities={
            AgeGroup.AGE_18_25: 0.15,
            AgeGroup.AGE_26_30: 0.20,
            AgeGroup.AGE_31_35: 0.15,
            # ... 其他年龄段
        },
        gender_probabilities={
            GenderType.MALE: 0.51,
            GenderType.FEMALE: 0.49
        },
        trend_probabilities={
            TrendType.STABLE: 0.2,
            TrendType.GROWTH: 0.2,
            TrendType.DECLINE: 0.4,
            TrendType.FLUCTUATION: 0.2
        },
        district_probabilities={
            "朝阳区": 0.15,
            "海淀区": 0.12,
            # ... 其他区县
        },
        age_base_params=age_base_params,
        beijing_districts=beijing_districts
    )
    
    # 在模拟器配置中使用
    config = SimulatorConfig(
        num_users=1000,
        distribution_config=distribution_config
    )
    ```
    
    字段说明：
    - age_probabilities: 年龄分布概率，控制不同年龄段用户的比例
    - gender_probabilities: 性别分布概率，控制男女用户的比例
    - trend_probabilities: 趋势类型分布概率，控制不同消费趋势用户的比例
    - district_probabilities: 区县分布概率，控制不同地区用户的比例
    - age_base_params: 年龄段基础参数映射，为每个年龄段提供基础参数
    - beijing_districts: 北京市各区县街道数据，提供地理位置信息
    """
    age_probabilities: Dict[AgeGroup, float] = Field(..., description="年龄分布概率")
    gender_probabilities: Dict[GenderType, float] = Field(..., description="性别分布概率")
    trend_probabilities: Dict[TrendType, float] = Field(..., description="趋势类型分布概率")
    district_probabilities: Dict[str, float] = Field(..., description="区县分布概率")
    age_base_params: Dict[AgeGroup, AgeBaseParams] = Field(..., description="年龄段基础参数")
    beijing_districts: Dict[str, List[str]] = Field(..., description="北京市各区县街道数据")
    
    @field_validator('age_probabilities', 'gender_probabilities', 'trend_probabilities', 'district_probabilities')
    @classmethod
    def probabilities_sum_to_one(cls, v):
        """验证概率分布总和必须为1"""
        total = sum(v.values())
        if not 0.99 <= total <= 1.01:  # 允许小的浮点误差
            raise ValueError(f'概率总和必须为1，当前为{total}')
        return v


class SimulatorConfig(BaseModel):
    """
    模拟器配置类
    
    这个类是移动资费数据模拟器的顶层配置类，整合了所有必要的配置参数。
    它定义了模拟器的运行参数、输出设置、分布配置等，是整个模拟系统的核心配置。
    
    用途：
    - 定义模拟器的运行参数和规模
    - 配置数据输出和存储方式
    - 整合分布配置和数据库配置
    - 控制模拟过程的随机性和可重复性
    
    依赖关系：
    - 被依赖：EnhancedMobileDataSimulator
    - 依赖：DistributionConfig, pydantic.BaseModel, pydantic.Field
    
    使用场景：
    1. 在 EnhancedMobileDataSimulator 中作为主要的配置输入
    2. 在数据生成过程中控制模拟的规模和特性
    3. 在配置管理中提供统一的配置接口
    
    示例用法：
    ```python
    # 创建模拟器配置
    config = SimulatorConfig(
        num_users=1000,                    # 生成1000个用户
        num_months=24,                     # 生成24个月数据
        output_file="data/user_data.csv",  # 输出文件路径
        random_seed=42,                    # 随机种子，确保可重复性
        distribution_config=distribution_config,  # 分布配置
        database_config={                  # 数据库配置（可选）
            "db_type": "sqlite",
            "database": "user_data.db"
        }
    )
    
    # 在模拟器中使用
    simulator = EnhancedMobileDataSimulator(config)
    df = simulator.run_simulation(save_csv=True, save_sqlite=True)
    ```
    
    字段说明：
    - num_users: 用户数量，控制生成数据的规模
    - num_months: 生成月数，控制数据的时间跨度
    - output_file: 输出文件名，指定CSV文件的保存路径
    - random_seed: 随机种子，确保数据生成的可重复性
    - distribution_config: 分布配置，定义用户群体的统计特征
    - database_config: 数据库配置，用于数据存储（可选）
    """
    num_users: int = Field(default=1000, description="用户数量", gt=0)
    num_days: Optional[int] = Field(default=None, description="生成天数", gt=0)
    num_months: Optional[int] = Field(default=None, description="生成月数", gt=0)
    output_file: str = Field(default="data/用户移动资费模拟数据.csv", description="输出文件名")
    random_seed: Optional[int] = Field(default=42, description="随机种子")
    distribution_config: DistributionConfig = Field(..., description="分布配置")
    start_date: datetime = Field(default=datetime(2025, 7, 30), description="开始日期")
    
    # 数据库配置
    database_config: Optional[Dict] = Field(None, description="数据库配置")
    
    @model_validator(mode='after')
    def validate_time_period(self):
        """验证时间周期参数"""
        if self.num_days is None and self.num_months is None:
            raise ValueError('必须指定num_days或num_months中的一个')
        return self


class DatabaseConfig(BaseModel):
    """
    数据库配置类
    
    这个类定义了数据库连接的配置参数，支持MySQL和SQLite两种数据库类型。
    它提供了灵活的数据库配置选项，支持不同的部署环境和存储需求。
    
    用途：
    - 配置数据库连接参数
    - 支持多种数据库类型
    - 提供灵活的存储选项
    - 支持不同的部署环境
    
    依赖关系：
    - 被依赖：EnhancedMobileDataSimulator, 数据库操作相关类
    - 依赖：pydantic.BaseModel, pydantic.Field
    
    使用场景：
    1. 在 EnhancedMobileDataSimulator 中配置数据存储
    2. 在数据库操作中提供连接参数
    3. 支持不同的部署环境和存储需求
    
    示例用法：
    ```python
    # SQLite数据库配置
    sqlite_config = DatabaseConfig(
        db_type="sqlite",
        database="data/user_data.db",
        table_name="user_records"
    )
    
    # MySQL数据库配置
    mysql_config = DatabaseConfig(
        db_type="mysql",
        host="localhost",
        port=3306,
        username="root",
        password="password",
        database="mobile_data",
        table_name="user_records"
    )
    
    # 在模拟器中使用
    simulator = EnhancedMobileDataSimulator(config)
    df = simulator.run_simulation(
        save_sqlite=True,
        db_config=sqlite_config
    )
    ```
    
    字段说明：
    - db_type: 数据库类型，支持"mysql"和"sqlite"
    - host: 主机地址，MySQL数据库需要指定
    - port: 端口号，MySQL数据库需要指定
    - username: 用户名，MySQL数据库需要指定
    - password: 密码，MySQL数据库需要指定
    - database: 数据库名，指定要使用的数据库
    - table_name: 表名，指定要使用的数据表
    """
    db_type: str = Field(..., description="数据库类型", pattern="^(mysql|sqlite)$")
    host: Optional[str] = Field(None, description="主机地址")
    port: Optional[int] = Field(None, description="端口号")
    username: Optional[str] = Field(None, description="用户名")
    password: Optional[str] = Field(None, description="密码")
    database: str = Field(..., description="数据库名")
    table_name: str = Field(default="user_data", description="表名")
